package com.zx;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public class ResourceResolver {
    Logger logger = LoggerFactory.getLogger(getClass());
    private String basePackage;

    public ResourceResolver(String basePackage) {
        this.basePackage = basePackage;
    }

    public <R> List<R> scan(Function<String, R> mapper) {
        //1.获取路径下的所有资源
        //1.1 处理basePackage路径格式，用/替换.
        String basePackagePath = this.basePackage.replace(".", "/");
        String path = basePackagePath;
        try {
            List<R> collector = new ArrayList<>();
            scan0(basePackagePath, path, collector, mapper);
            return collector;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public <R> void scan0 (String basePackagePath, String path, List<R> collector, Function<String, R> mapper) throws IOException, URISyntaxException {
        logger.atDebug().log("scan path: {}", path);
        //1.2 获取路径下的资源
        //获取上下文类加载器，通过getResources方法获取资源集合
        Enumeration<URL> en = getContextClassLoader().getResources(path);
        //遍历资源
        while (en.hasMoreElements()) {
            URL url = en.nextElement();
            URI uri = url.toURI();
            //获取资源路径字符串，去掉字符串结尾处的/或者\字符
            String uriStr = removeTrailingSlash(uriToString(uri));
            //截取资源路径字符串
            String uriBaseStr = uriStr.substring(0, uriStr.length() - basePackagePath.length());
            //如果路径是以file:开头，则截取掉file:，保留剩余部分
            if (uriBaseStr.startsWith("file:")) {
                uriBaseStr = uriBaseStr.substring(5);
            }
            if (uriStr.startsWith("jar:")) {
                //jar包中的资源，需要通过jarUriToPath方法生成Path对象
                scanFile(true, uriBaseStr, jarUriToPath(basePackagePath, uri), collector, mapper);
            } else {
                scanFile(false, uriBaseStr, Paths.get(uri), collector, mapper);
            }
        }
    }

    public ClassLoader getContextClassLoader() {
        //通过当前线程的getContextClassLoader方法获取类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = getClass().getClassLoader();
        }
        return cl;
    }

    public String uriToString(URI uri) {
        return URLDecoder.decode(uri.toString(), StandardCharsets.UTF_8);
    }

    public String removeTrailingSlash(String str) {
        if (str.endsWith("/") || str.endsWith("\\")) {
            str = str.substring(0, str.length()-1);
        }
        return str;
    }

    public Path jarUriToPath(String basePackagePath, URI jarUri) throws IOException {
        return FileSystems.newFileSystem(jarUri, Map.of()).getPath(basePackagePath);
    }

    public <R> void scanFile(boolean isJar, String base, Path root, List<R> collector, Function<String, R> mapper) throws IOException {
        String baseDir = removeTrailingSlash(base);
        //遍历资源路径下的资源
        Files.walk(root)
                .filter(Files::isRegularFile)
                .forEach(file -> {
                    String path = "";
                    if (isJar) {
                        //jar包中的资源，通过file.toString方法就可以拿到全限定名路径
                        path = removeLeadingSlash(file.toString());
                    } else {
                        //classpath下的资源，需要截取字符串拿到全限定名路径
                        String substring = file.toString().substring(baseDir.length());
                        path = removeLeadingSlash(substring);
                    }
                    R r = mapper.apply(path);
                    if (r != null) {
                        collector.add(r);
                    }
                });
    }

    public String removeLeadingSlash(String str) {
        if (str.startsWith("/") || str.startsWith("\\")) {
            str = str.substring(1);
        }
        return str;
    }
}
